
#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include "ModbusProt.h"

#define MODBUS_RTU_DEV_DEMO "/dev/ttymxc1"

/**
 * @brief modbus rtu 读取数据
 * @param ptModbus modbus使用参数结构体指针
 * @param ptMbTable modbus rtu 点表
 * @param iTableSize 点表大小
 * @param iStartAddr 起始地址
 * @param pData 数据指针
 * @return -1failed, other success
 */
static int _modbus_rtu_read(TModbus *ptModbus, TModbusTable *ptMbTable, int iTableSize, int iStartAddr, void *pData)
{
    int iRet = -1;
    if (ptModbus == NULL || ptModbus->Modbus == NULL || ptMbTable == NULL) {
        return e_ModbusRtuErrPtrNull;
    }
    MODBUS_LOCK(&ptModbus->Lock);
    /* 查询点表中是否存在该地址 */
    for (int i = 0; i < iTableSize; i++) {
        if (ptMbTable[i].StartAddr == iStartAddr) {
            for (int j = 0; j < MODBUS_RESEND_CNT; j++) {
                switch (ptMbTable[i].FunctionCode) {
                    case MODBUS_FC_READ_COILS:
                        iRet = modbus_read_bits(ptModbus->Modbus, ptMbTable[i].StartAddr, ptMbTable[i].RegNumber, (uint8_t *)pData);
                        break;
                    case MODBUS_FC_READ_DISCRETE_INPUTS:
                        iRet = modbus_read_input_bits(ptModbus->Modbus, ptMbTable[i].StartAddr, ptMbTable[i].RegNumber, (uint8_t *)pData);
                        break;
                    case MODBUS_FC_READ_HOLDING_REGISTERS:
                        iRet = modbus_read_registers(ptModbus->Modbus, ptMbTable[i].StartAddr, ptMbTable[i].RegNumber, (uint16_t *)pData);
                        break;
                    case MODBUS_FC_READ_INPUT_REGISTERS:
                        iRet = modbus_read_input_registers(ptModbus->Modbus, ptMbTable[i].StartAddr, ptMbTable[i].RegNumber, (uint16_t *)pData);
                        break;
                    default:
                        break;
                }
                if (iRet >= 0) {
                    ptModbus->IsComm = true;
                    break;
                }
            }
            break;
        }
    }
    MODBUS_UNLOCK(&ptModbus->Lock);
    return iRet;
}

/**
 * @brief modbus rtu 写入数据
 * @param ptModbus modbus使用参数结构体指针
 * @param ptMbTable modbus rtu 点表
 * @param iTableSize 点表大小
 * @param iStartAddr 起始地址
 * @param pData 数据指针
 * @return -1failed, other success
 */
static int _modbus_rtu_write(TModbus *ptModbus, TModbusTable *ptMbTable, int iTableSize, int iStartAddr, void *pData)
{
    int iRet = -1;
    if (ptModbus == NULL || ptModbus->Modbus == NULL || ptMbTable == NULL) {
        return e_ModbusRtuErrPtrNull;
    }
    MODBUS_LOCK(&ptModbus->Lock);
    /* 查询点表中是否存在该地址 */
    for (int i = 0; i < iTableSize; i++) {
        if (ptMbTable[i].StartAddr == iStartAddr) {
            for (int j = 0; j < MODBUS_RESEND_CNT; j++) {
                switch (ptMbTable[i].FunctionCode) {
                    case MODBUS_FC_WRITE_SINGLE_COIL:
                        iRet = modbus_write_bit(ptModbus->Modbus, ptMbTable[i].StartAddr, *((uint8_t *)pData));
                        break;
                    case MODBUS_FC_WRITE_SINGLE_REGISTER:
                        iRet = modbus_write_register(ptModbus->Modbus, ptMbTable[i].StartAddr, *((uint16_t *)pData));
                        break;
                    case MODBUS_FC_WRITE_MULTIPLE_REGISTERS:
                        iRet = modbus_write_registers(ptModbus->Modbus, ptMbTable[i].StartAddr, ptMbTable[i].RegNumber, (uint16_t *)pData);
                        break;
                    default:
                        break;
                }
                if (iRet >= 0) {
                    ptModbus->IsComm = true;
                    break;
                }
            }
            break;
        }
    }
    MODBUS_UNLOCK(&ptModbus->Lock);
    return iRet;
}

/**
 * @brief modbus rtu 重连函数
 * @param ptModbus modbus使用参数结构体指针
 */
static void _modbus_rtu_reconnect(TModbus *ptModbus)
{
    MODBUS_LOCK(&ptModbus->Lock);
    ptModbus->IsComm = false;
    /* 如果当前已经处于连接状态，则断开 */
    if (ptModbus->IsConnect) {
        ptModbus->Onclose(ptModbus->Modbus);
    }
    /* 连接从机，如果连接失败 */
    if (ptModbus->OnConnect(ptModbus->Modbus) == -1) {
        ptModbus->IsConnect = false;
    }
    /* 连接成功 */
    else {
        ptModbus->IsConnect = true;
    }
    MODBUS_UNLOCK(&ptModbus->Lock);
    return;
}

/**
 * @brief 创建 modbus rtu 主站对象
 * @param tModbusRtuCfg modbus rtu 配置
 * @return TModbus *
 */
TModbus *modbus_rtu_master_create(TModbusRtuCfg tModbusRtuCfg)
{
    int      iRet = 0;
    TModbus *ptModbus = (TModbus *)malloc(sizeof(TModbus));
    if (ptModbus) {
        memset(ptModbus, 0, sizeof(*ptModbus));
        iRet = pthread_mutex_init(&ptModbus->Lock, NULL);
        if (iRet != 0) {
            printf("%s >> pthread_mutex_init failed\n", __func__);
            free(ptModbus);
            ptModbus = NULL;
            return NULL;
        }

        /* 设置Rtu信息 */
        ptModbus->Modbus = modbus_new_rtu(tModbusRtuCfg.DevName, tModbusRtuCfg.BaudRate, tModbusRtuCfg.Parity, tModbusRtuCfg.DataBit, tModbusRtuCfg.StopBit);
        if (ptModbus->Modbus == NULL) {
            printf("%s >> modbus_new_rtu failed, Err: %s\n", __func__, modbus_strerror(errno));
            free(ptModbus);
            ptModbus = NULL;
            return NULL;
        }

        /* 是否设置调试模式 */
        iRet = modbus_set_debug(ptModbus->Modbus, tModbusRtuCfg.IsDebug);
        if (iRet == -1) {
            printf("%s >> modbus_set_debug failed, Err: %s\n", __func__, modbus_strerror(errno));
            modbus_free(ptModbus->Modbus);
            free(ptModbus);
            ptModbus = NULL;
            return NULL;
        }

        /* 设置设备地址 */
        iRet = modbus_set_slave(ptModbus->Modbus, tModbusRtuCfg.DevId);
        if (iRet == -1) {
            printf("%s >> modbus_set_slave failed, Err: %s\n", __func__, modbus_strerror(errno));
            modbus_free(ptModbus->Modbus);
            free(ptModbus);
            ptModbus = NULL;
            return NULL;
        }

        /* 设置响应超时时间 */
        iRet = modbus_set_response_timeout(ptModbus->Modbus, tModbusRtuCfg.TimeoutSec, tModbusRtuCfg.TimeoutUSec);
        if (iRet == -1) {
            printf("%s >> odbus_set_response_timeout failed, Err: %s\n", __func__, modbus_strerror(errno));
            modbus_free(ptModbus->Modbus);
            free(ptModbus);
            ptModbus = NULL;
            return NULL;
        }

        /* 连接从站 */
        iRet = modbus_connect(ptModbus->Modbus);
        if (iRet == -1) {
            ptModbus->IsConnect = false;
            printf("%s >> modbus_connect failed, Err: %s\n", __func__, modbus_strerror(errno));
        }
        else {
            ptModbus->IsConnect = true;
            ptModbus->IsComm = true;
        }

        /* 获取串口模式 */
        iRet = modbus_rtu_get_serial_mode(ptModbus->Modbus);
        if (iRet != MODBUS_RTU_RS485) {
            /* 设置为485串口模式 */
            modbus_rtu_set_serial_mode(ptModbus->Modbus, MODBUS_RTU_RS485);
        }

        ptModbus->OnConnect = modbus_connect;
        ptModbus->Onclose = modbus_close;
        ptModbus->OnReconnect = _modbus_rtu_reconnect;
        ptModbus->OnRead = _modbus_rtu_read;
        ptModbus->OnWrite = _modbus_rtu_write;
    }

    return ptModbus;
}

void modbus_rtu_master_demo(void)
{
    int           iRet = 0;
    int           iData = 100;
    TModbusRtuCfg tTable[] = {
        { MODBUS_RTU_DEV_DEMO, 9600, 'N', 8, 1, 0x01, false, 0, 1000 * 300 },
    };
    TModbusTable tWriteTable[] = {
        /* 起始地址  寄存器数量  功能码 */
        { 0, 1, MODBUS_FC_WRITE_SINGLE_REGISTER },
    };
    TModbus *ptModbus = modbus_rtu_master_create(tTable[0]);
    if (ptModbus == NULL) {
        printf("ptModbus if null\n");
        return;
    }
    while (1) {
        iRet = ptModbus->OnWrite(ptModbus, tWriteTable, MODBUS_TABLE_SIZE(tWriteTable), 0, &iData);
        /* 通讯失败重连 */
        if (iRet < 0) {
            ptModbus->OnReconnect(ptModbus);
        }
        printf("%s iRet: %d, iData: %d\n", __func__, iRet, iData++);
    }
}
